Hey all, is there a way with Supertokens to handle...
# support-questions-legacy
h
Hey all, is there a way with Supertokens to handle account management ? I have multiple users with different roles (admin, user) linked to one account. These users can only retrieve data from entries linked to that account. Pretty common for B2B apps. How do you deal with that ? Thanks a lot
r
Hey @hhchift we do have roles support. Checkout out roles recipe docs: https://supertokens.com/docs/userroles/introduction
h
Hey, thanks for the reply. I'm more looking to something similar to this for example: https://auth0.com/docs/manage-users/organizations . A user can belong to one or more group/organization and has roles in that group/organization.
r
Ah right. We do not yet have the abstraction of orgs. But you can emulate it by modifying the user's email during sign up to contain an org ID. This would generate a unique userID for that user even if they use the same email across orgs. This userId can then be assigned roles to it.
h
Ok, I see the idea. Could you force to only use the same password if you have two accounts ? I'm working with invitation emails to an account. 1) User receives email to join organisation 2) User go on a page where he creates his user, the userID is automatically filled with the orgId 3) User receives a second email invitation to join a second organisation 4) User go on a page where he signs in. I assume you can here create a second user in the background ?
r
Yes. Enforcing the same password is possible.
Can you explain the scenario with a clear example please? So that I can show the write pseudo code. The more detailed the example, the better.
h
The goal is to be able to manage users & accounts (organizations) and the role between the user & the accounts. Example: One user Henry can be admin of AccountA One user Matthieu can be a read-only user of AccountA Same user Matthieu can as well be admin of AccountB The same user has only one credentials (email/password) to login to all his accounts. In the web app, he can switch between his accounts without having to log again. One login page to login on the platform where he can switch between his accounts. Thanks
Clear for you ? thanks
r
> he can switch between his accounts without having to log again. How will the app know which account a user is currently logged into? For example, if the user is on a.example.com, does that mean that they are in accountA, and if they are on b.example.com it means that they are on accountB? or some other method?
h
I would actually prefer that he simply logs into the web site with one login page on example.com He has a default account selected. With his settings, he can switch between his accounts. You keep the last logged account in the localStorage.
r
ah i see. Makes sense.
Thanks for the description. This is certainly possible.. give me 10-15 mins
So what you want to do is as follows: - A user signs up / signs in as usual - no change to their email ID / user ID there. - When the user selects an account (post sign in), you want to modify the session's access token payload to reflect the account they have selected. Let's say you do this by changing the payload to have a key
accountSelected: "accountID"
- Now, when associating a role to the user's account, you can call the
UserRoles.addRoleToUser
function with the
userId
value as
userId+accountId
. And so this way, you are scoping the roles for that user to be per account. So whenever you want to get a list of roles that the user has, you would need to pass in the string
userId+accountId
to the
UserRoles
functions. And you could get the current selected accountId from the session.
h
That could work indeed, and the addRoleToUser method will work with a userId that does not exist ? (as the userId = userId+accountId)
r
correct.
it's made as a separate "recipe" so it just works based on an input ID
h
And on the backend you can restrict API routes by extracting the userId & the accountId from the session and check the roles ?
r
exactly.
h
Looks like a flexible approach 🙂 thanks!
r
happy to help 🙂
h
Is there a way to handle email invitations ?
r
You mean you want to disable sign up and send a link to users asking them to set a password?
h
Yes indeed
I don't have access
r
tagged you.
do you have access now?
h
Yes thanks
Hey @rp_st , I come back on this way of working with accounts. How can I add the roles to the session ? I see that there is a function for that but I will probably have to override it to work with a userId+accountId combination. And how can it be retrieved from the session ? Moreover, how can I retrieve all the roles from a specific userId to be able to see to which accounts a user is linked ?
r
Hey! You can directly pass in the userId + accountId string to the user roles functions and it will work with that string as the identifier
h
I'm not sure to follow
r
So you modify the user ID to be userId+accountId correct? In the sign up function return?
h
No I don't modify the user ID. I don't think this was the idea. The idea was to use userId+accountId as the userId in the addRoleToUser
r
oh right yeaaa.
h
My user needs to be able to have roles to multiple accounts
r
So right now, where do you assign users roles? In the post sign up override?
h
Yes, but it can happen as well later on ...
You could add a role to an existing user to an account for example
r
right ok. So whenever you add a role to a user, you want to make sure to update the user's session to reflect that as well using the
await session.fetchAndSetClaim(UserRoleClaim)
as mentioned here: https://supertokens.com/docs/userroles/managing-roles-and-users Now the
UserRoleClaim
internally will call the getRolesForUser function from the roles recipe. The input to this function is the userId + userContext object. You want to override this function to take the input userId and get the session from the userContext (which you will have to add to it), and then form the actual ID (userId+accountId) and then call the original implementaiton.
The only really tricky part here is to make sure that you make the session objecty available to you in the user role recipe overrides.
h
any example of code of this ?
on how to override UserRoleClaim
r
you don't need to override UserRoleClaim
you're using the node repo?
h
yes
In need to override the getRolesForUser ?
r
yes. let me show you. Give me a few mins
Something like this:
Copy code
ts
UserRoles.init({
    override: {
        functions: (oI) => {
            return {
                ...oI,
                addRoleToUser: async function (input) {
                    let sessionContainer: SessionContainer = input.userContext.session;
                    if (sessionContainer === undefined) {
                        throw new Error("Don't know which account the user has selected currently");
                    } 
                    let currentAccountId = sessionContainer.getAccessTokenPayload()["currAccountId"];
                    input.userId = input.userId + "|" + currentAccountId
                    return oI.addRoleToUser(input);
                },
                getRolesForUser: async function (input) {
                    let sessionContainer: SessionContainer = input.userContext.session;
                    if (sessionContainer === undefined) {
                        throw new Error("Don't know which account the user has selected currently");
                    } 
                    let currentAccountId = sessionContainer.getAccessTokenPayload()["currAccountId"];
                    input.userId = input.userId + "|" + currentAccountId
                    return oI.getRolesForUser(input);
                },
                getUsersThatHaveRole: async function (input) {
                    let resp = await oI.getUsersThatHaveRole(input);
                    if (resp.status === "OK") {
                        resp.users = resp.users.map(i => {
                            return i.split("|")[0];
                        });
                        // remove duplicate userIds from the array
                        resp.users = [...new Set(resp.users)];
                    }
                    return resp;
                }
            }
        }
    }
})
h
Powerful 🙂
Would it make senses as well to add the list of linked accounts in the UserMetaData so that it's possible to get all linked accounts ?
As otherwise I don't see how I can retrieve all the accounts linked to a user ?
r
you could store in there, or in your own db.
h
would it work if I add a second argument accountId on getRolesForUser
that would be used if passed otherwise it's taken from the session
as you did
r
that would not work as the function allows for just one argument. But you could always pass it in the userContext object instead, and read that just like how i am reading the session object from the userContext above.
h
Ok
From the sdk documentation, I see this:
addRoleToUser(userId: string, role: string, userContext?: any): Promise
3 inputs and the userContext passed as an input
the override you describe will work ?
r
ah this is the user exposed version of the function. The recipe version of the function just has
input
which contains these three things - but just in the object form
h
hmm what's the difference ? if I use addRoleToUser I can use it with the single input ?
r
You should follow the types when using a function, and it will work 🙂
h
ok for example like this: const response = await UserRoles.addRoleToUser(userId, "admin", { 'session': req.session });
ok I will give it a try, thanks!
r
> ok for example like this: const response = await UserRoles.addRoleToUser(userId, "admin", { 'session': req.session }); Yea, this works
h
await req.session.fetchAndSetClaim(UserRoleClaim);
this is not working ?
r
whats the error?
h
fetchAndClaim is not a function
r
which version of the SDK?
this feature is on the latest version only
h
"supertokens-node": "^11.0.2",
ok my bad
r
ah ok. U should consider updating
h
How can I make sure that my roles are always in my session ?
for example on the createNewSession
As in my frontend I don't see the permissions
I assume it's added when doing fetchAndSetClaim manually when I create an account for example
r
yea
the roles are.. if you use roles claim
there is a separate permissions claim as well which you can add
h
but what for the use case when sign in or refreshing the session
r
you can override the sign in API and add the roles and permissions to the session in the post override
refreshing will maintain the roles in the session
h
is there a way to force refresh the session from the front ?
and which route is called then in the backend ?
r
yea there is. The frontend SDK's session recipe should have a function like
attemptRefreshingSession
h
Thanks
Does the signOutPOST function exists in the recipe session ?
Is it possible to override it ? It does not seem possible
I would like to save the current account selected before signing out
r
Yea, it is possible to override it. It is in the session recipe indeed
session.init -> override -> apis -> signOutPOST
h
I have an incoming request in my backend on the Incoming request on /auth/signout
I have an override of the signOutPOST
but it never seems to be called
in my front (react), I call the signOut function
r
Can i see the override code please?
h
Copy code
signOutPOST: async function (input) {
                        console.log("signint out")
                        if (originalImplementation.signOutPOST === undefined) {
                            throw Error("Should never come here");
                        }
                        // First we call the original implementation of signUpPOST.
                        console.log('signing out')
                        let response = await originalImplementation.signOutPOST(input);
                        // make sure the roles are incorporated
                        return response;
                    },
r
and where have you added this function?
h
Copy code
Session.init({
        override: {
            functions: (originalImplementation) => {
                return {
                    ...originalImplementation, `
here under
r
ah. Change
functions
to
apis
h
can you explain the difference ?
createNewSession and refreshSession are working under functions for example
Exposed routes vs internal functions ? 🙂
r
correct
exposed routes call inetrnal functions
h
Sorry to bother but I'm having diffulcties to make sure that the roles are in the token on signIn. On signIn , my currAccountId is not yet in the payload as there is no payload, it's in the createNewSession that I based on the metaData put the currAccountId in the accessTokenPayload
at the end of my createNewSession I do this:
Copy code
let response =  await originalImplementation.createNewSession(input);
                        await response.fetchAndSetClaim(UserRoleClaim, { 'session': response });
                        await response.fetchAndSetClaim(PermissionClaim, { 'session': response });

                        return response;
but in my front the session does not seem to have the roles then
r
Hmm. Where do you assign the roles to the user?
h
that's already done
on the creation of the account as you suggested
but if the user log outs
and log back in
It needs to be retrieved again
you see my point ?
r
oh yea right. ok
can you print out
response.getAccessTokenPayload()
before each of the
fetchAndSetClaim
- what do you get?
h
something like this:
Copy code
{
  'st-role': { v: [], t: 1665678920120 },
  'st-perm': { v: [], t: 1665678919479 },
  currAccountId: 'e3892d4b-4186-4f75-9efe-6bb41eca1c8f'
}
in the createNewSession
r
hmm. So the value is empty, which means it's not able to find the role for the user.
can you print out the value of userContext in the override for roles recipe? Does it contain the
session
object that's being passed into it?
h
yes
but I see that the userId is good
but even the output of the getRole is empty
so probably something on my side , let me check
r
sure.
v
r
@vvaf. try now
4 Views